在 ES6 後,新增了 class 類別,一個更簡潔的語法來建立物件,也是建立繼承的語法糖。
必須再次強調,JavaScript 的 class 用法與其他 class-based OOP 語言中的 class 並不相同!雖然乍看寫法類似,但在 JS 中的 class 還是以原型 prototype 為基礎打造出來的,與 Java 等使用的 class 可不相同,可說是遠看像匹狼近看是隻羊,可別傻呼呼的衝進狼群內(如:Java) 咩咩咩的喊著我們都一樣唷~
(忘記 OOP 的請前往 D15 - 那個圓圓的東西 - OOP 物件導向程式設計 結束再回來)
Class 語法預計分為上下兩篇,此篇主要為基礎語法介紹
段落分為:
class 創建物件的方法與 constructor 相同,都是 new
+ class 名稱
建立實例,主要差異在定義的寫法上。
在 class 定義中也分為 class declaraion 與 class expression 兩種不同寫法:
class 定義時會將預計加入實例中的屬性寫入關鍵字 constructor
內,當使用 new
關鍵字時,便會走訪 constructor 內的程式碼並回傳入新物件,constructor 內怎麼寫實例內的 body code 就長怎樣。
一個 class 內只會有一個 constructor
// constructor 寫法
function Cake(size, flavor) {
this.size = size;
this.flavor = flavor;
}
let cake1 = new Cake('M','cream')
console.log( cake1 ) // Cake {size: "M", flavor: "cream"}
// class declaration 寫法
class Cake {
constructor(size, flavor) {
this.size = size;
this.flavor = flavor;
}
}
// class expression 寫法
let Cake = class {
constructor(size, flavor) {
this.size = size;
this.flavor = flavor;
}
}
let cake2 = new Cake('M','choco');
console.log(cake2) // Cake {size: "M", flavor: "choco"}
使用 constructor 時,共享的 method 會另外放在 prototype 物件內,因此建立的實例 instance 都可以透過 __proto__
在原型鏈連結到此物件; 而 class 語法將這做法簡化,shared method 不需另外寫在 prototype 物件上,可以直接寫入 class body 內。
// construcotr 的共享屬性
function Cake(flavor) {
this.flavor = flavor;
}
Cake.prototype = {
price: (cost) => `NTD ${cost* 1.5 }`
}
let cake1 = new Cake('cream')
cake1.price(50) // NTD 75
// class 將共享的 method 寫在 class 內
class Cake {
constructor(flavor) {
this.flavor = flavor;
}
price(cost) {
return `NTD$ {cost* 1.5 }`
}
}
let cake1 = new Cake('choco')
cake1.price(50) // NTD 75
透過 devTool 查看,果然 price method 是放在 Cake 的 prototype 物件內
extends
, super
擴充類別用 extends
關鍵字可以擴充繼承的類別,將 extends
後的類別加入原型鏈上!
先寫個簡單版看看怎麼使用 extends
增加類別
class Pastry {}
class Cake extends Pastry {} // 增加一個類別 Pastry
let cake1 = new Cake()
透過 extends
可以很方便地將任何的 constructor 或 class 加入原型鏈成為 parentClass,使用 proto 查訪新加入的原型鏈成員。
Cake.prototype.__proto__ === Pastry.prototype
cake1.__proto__.__proto__ === Pastry.prototype // true
cake1 instanceof Pastry // true
接下來,試試除了擴增類別外,也在本身 class 內定義屬性,剛剛學過的,寫在 constructor 內,像這樣嗎?
class Cake extends Pastry {
constructor(flavor) {
this.flavor = flavor;
}
price(cost) {
return `NTD${cost* 1.5 }`
}
}
噢,不!
這可是會出現錯誤訊息哦
Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
錯誤訊息顯示,在使用子類別的 this 前,必須先調用母階建構函式。
先說解法就是:在子類別的 constructor 中先使用 super
呼叫母類別。
那,這是為什麼呢?
我自己的記法是 extends
指定了類別的階層,而 super
這個動作才是真正在物件上依照原型鏈順序建立屬性。
當要在子類別中使用 this 時,須先使用 super
呼叫並執行母類別的物件,先建立好母類別指定的屬性後,再接續建立子類別中的屬性,這樣也才符合為什麼 childClass 可以覆蓋 parentClass 的特性呀。
正確的程式碼應該像這樣:
class Pastry {
constructor(name){
this.slogan = `${name}愛吃不怕胖`
}
calories(piece){
return `熱量${piece*480}k`
}
}
class Cake extends Pastry {
constructor(flavor, name) {
super(name) // 先使用 super 呼叫並執行 Pastry
this.flavor = flavor;
}
price(cost) {
return `NTD${cost* 1.5 }`
}
}
let cake1 = new Cake('choco', 'Hoo')
console.log( cake1 ) // Cake {slogan: "Hoo愛吃不怕胖", flavor: "choco"}
console.log( cake1.calories(3)) // 熱量1440k
static
定義靜態方法 Static Method靜態方法 static method 存在定義的 class 中,只能由 class 存取,建立的 實例 instance 不能取到,靜態方法的語法是在前面加上 static
。
class Cake {
constructor(flavor) {
this.flavor = flavor;
}
price(cost) {
return `NTD${cost* 1.5 }`
}
static changeFlavor(flavor){
this.flavor = flavor;
console.log(`flavor has changed to ${flavor}`)
}
}
let cake1 = new Cake('choco')
cake1.changeFlavor('mocha') // error; cake1.private() is not a function
Cake.changeFlavor('mocha') // flavor has changed to mocha
// 可以透過 call 的方式綁定 this 為實例 cake1 進而修改
Cake.changeFlavor.call(cake1, 'mocha') // flavor has changed to mocha
console.log(cake1.flavor) // mocha
constructor
內,class 大括號內只有一個 construtor。extends
擴充類別,寫在 extends
前的為 childClass,extends
後的為 parentClasssuper
用來呼叫 parentClass,加在 constructor 內的 this 程式碼之前static
開頭後的 method 只有 class 可以呼叫,稱為靜態方法JavaScript | ES6 中最容易誤會的語法糖 Class - 基本用法
MDN - class
Class basic syntax
[JS] JavaScript 類別(Class)
JavaScript Class
基本的 Class 語法介紹完畢!
明天內容為 constructor 與 class 的差異比較、真正的 class 與 prototyped-based 的 class 差異。
return NTD$ {cost* 1.5 }
的$符合位置是不是要 return NTD ${cost* 1.5 }